using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using Microsoft.Surface;
using Microsoft.Surface.Core;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace XNAApplication
{
    /// <summary>
    /// This is the main type for your application.
    /// </summary>
    public class App1 : Microsoft.Xna.Framework.Game
    {
        private readonly GraphicsDeviceManager graphics;
        private SpriteBatch spriteBatch;

        private TouchTarget touchTarget;
        private Color backgroundColor = new Color(81, 81, 81);
        private bool applicationLoadCompleteSignalled;

        private UserOrientation currentOrientation = UserOrientation.Bottom;
        private Matrix screenTransform = Matrix.Identity;

        Model myModel;
        Vector3 modelPosition = Vector3.Zero;
        Vector3 cameraPosition = new Vector3(0.0f, 50.0f, 5000.0f);

        float modelRotationY = 0.0f;
        float modelRotationX = 0.0f;
        float modelRotationZ = 0.0f;
        float aspectRatio;

        Dictionary<int, TouchPoint> touchPoints;
        Dictionary<int, TouchPoint> menuPoints;
        
        //variable needed for orbit function
        TouchPoint oldPoint;

        //variables needed for scaling function
        TouchPoint pinchFingerOneOld;
        TouchPoint pinchFingerTwoOld;
        float modelScale = 45.0f;

        //variable for showing menucomponent
        MenuComponent menuComponent;

        //variable needed for z-rotation function
        Dictionary<int, TouchPoint> zRotationPoints;



        /// <summary>
        /// The target receiving all surface input for the application.
        /// </summary>
        protected TouchTarget TouchTarget
        {
            get { return touchTarget; }
        }

        /// <summary>
        /// Default constructor.
        /// </summary>
        public App1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        #region Initialization

        /// <summary>
        /// Moves and sizes the window to cover the input surface.
        /// </summary>
        private void SetWindowOnSurface()
        {
            System.Diagnostics.Debug.Assert(Window != null && Window.Handle != IntPtr.Zero,
                "Window initialization must be complete before SetWindowOnSurface is called");
            if (Window == null || Window.Handle == IntPtr.Zero)
                return;

            // Get the window sized right.
            Program.InitializeWindow(Window);
            // Set the graphics device buffers.
            graphics.PreferredBackBufferWidth = Program.WindowSize.Width;
            graphics.PreferredBackBufferHeight = Program.WindowSize.Height;
            graphics.ApplyChanges();
            // Make sure the window is in the right location.
            Program.PositionWindow();
        }

        /// <summary>
        /// Initializes the surface input system. This should be called after any window
        /// initialization is done, and should only be called once.
        /// </summary>
        private void InitializeSurfaceInput()
        {
            System.Diagnostics.Debug.Assert(Window != null && Window.Handle != IntPtr.Zero,
                "Window initialization must be complete before InitializeSurfaceInput is called");
            if (Window == null || Window.Handle == IntPtr.Zero)
                return;
            System.Diagnostics.Debug.Assert(touchTarget == null,
                "Surface input already initialized");
            if (touchTarget != null)
                return;

            // Create a target for surface input.
            touchTarget = new TouchTarget(Window.Handle, EventThreadChoice.OnBackgroundThread);
            touchTarget.TouchDown += new EventHandler<TouchEventArgs>(touchTarget_TouchDown);
            touchTarget.TouchUp += new EventHandler<TouchEventArgs>(touchTarget_TouchUp);
            touchTarget.EnableInput();
        }

        #endregion

        #region Overridden Game Methods

        /// <summary>
        /// Allows the app to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here

            IsMouseVisible = true; // easier for debugging not to "lose" mouse
            SetWindowOnSurface();
            InitializeSurfaceInput();

            // Set the application's orientation based on the orientation at launch
            currentOrientation = ApplicationServices.InitialOrientation;

            // Subscribe to surface window availability events
            ApplicationServices.WindowInteractive += OnWindowInteractive;
            ApplicationServices.WindowNoninteractive += OnWindowNoninteractive;
            ApplicationServices.WindowUnavailable += OnWindowUnavailable;

            // Setup the UI to transform if the UI is rotated.
            // Create a rotation matrix to orient the screen so it is viewed correctly
            // when the user orientation is 180 degress different.
            Matrix inverted = Matrix.CreateRotationZ(MathHelper.ToRadians(180)) *
                       Matrix.CreateTranslation(graphics.GraphicsDevice.Viewport.Width,
                                                 graphics.GraphicsDevice.Viewport.Height,
                                                 0);

            if (currentOrientation == UserOrientation.Top)
            {
                screenTransform = inverted;
            }

            touchPoints = new Dictionary<int, TouchPoint>();
            menuPoints = new Dictionary<int, TouchPoint>();
            zRotationPoints = new Dictionary<int, TouchPoint>();

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per app and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            // TODO: use this.Content to load your application content here
            myModel = Content.Load<Model>("Models\\p1_wedge");
            aspectRatio = graphics.GraphicsDevice.Viewport.AspectRatio;


            //menu 
            string[] menuItems = { "Option 1", "Option 2", "Option 3" };

            spriteBatch = new SpriteBatch(GraphicsDevice);

            menuComponent = new MenuComponent(this,
                spriteBatch,
                Content.Load<SpriteFont>("menuFont"),
                menuItems);
        }

        /// <summary>
        /// UnloadContent will be called once per app and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the app to run logic such as updating the world,
        /// checking for collisions, gathering input and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            if (ApplicationServices.WindowAvailability != WindowAvailability.Unavailable)
            {
                if (ApplicationServices.WindowAvailability == WindowAvailability.Interactive)
                {
                    // TODO: Process touches, 
                    // use the following code to get the state of all current touch points.
                    // ReadOnlyTouchPointCollection touches = touchTarget.GetState();
                    ReadOnlyTouchPointCollection touches = touchTarget.GetState();

                    foreach(TouchPoint touch in touches)
                    {
                        if(touch.IsFingerRecognized)
                        {
                            touchPoints[touch.Id] = touch;
                        }
                        if(touch.IsTagRecognized)
                        {
                            menuPoints[touch.Id] = touch;
                        }
                    }

                    if (touchPoints.Count > 0)
                    {
                        if (touchPoints.Count == 1) updateOrbitWithTouch(touchPoints.First().Value, gameTime);
                        if (touchPoints.Count == 2) pinchWithTouches(gameTime);
                        if (touchPoints.Count == 5) rotateZAxis(gameTime);
                    }


                    //if (touches.Count > 0 && touches[0].IsFingerRecognized == true)
                    //{
                        
                    //}
                    //else if (touches.Count > 0)
                    //{
                    //    foreach(TouchPoint t in touches)
                    //    {
                    //        if(t.IsTagRecognized)
                    //        {
                    //            menuPoint = t;
                    //            break;
                    //        }
                    //    }
                    //}
                }

                // TODO: Add your update logic here
            }

            base.Update(gameTime);
        }

        private void updateOrbitWithTouch(TouchPoint touch, GameTime gameTime)
        {
            if (oldPoint == null || oldPoint.Id != touch.Id) oldPoint = touch;

            float differenceX = touch.X - oldPoint.X;
            float differenceY = touch.Y - oldPoint.Y;

            modelRotationY += differenceX * MathHelper.ToRadians(0.1f);
            modelRotationX += differenceY * MathHelper.ToRadians(0.1f);
            // System.Diagnostics.Debug.WriteLine("Modelrotation changed = " + modelRotation);
            oldPoint = touch;
        }

        private void pinchWithTouches(GameTime gameTime)
        {
            TouchPoint pinchOne;
            TouchPoint pinchTwo;

            if (pinchFingerOneOld == null || pinchFingerTwoOld == null)
            {
                pinchFingerOneOld = touchPoints.First().Value;
                pinchFingerTwoOld = touchPoints.Last().Value;
            }

            if (touchPoints.ContainsKey(pinchFingerOneOld.Id) && touchPoints.ContainsKey(pinchFingerTwoOld.Id))
            {
                pinchOne = touchPoints[pinchFingerOneOld.Id];
                pinchTwo = touchPoints[pinchFingerTwoOld.Id];
            }
            else
            //if(pinchOne == null || pinchTwo == null)
            {
                pinchOne = touchPoints.First().Value;
                pinchTwo = touchPoints.Last().Value;
                pinchFingerOneOld = pinchOne;
                pinchFingerTwoOld = pinchTwo;
            }

            float distanceOld = Vector2.Distance(new Vector2(pinchFingerOneOld.X, pinchFingerOneOld.Y), new Vector2(pinchFingerTwoOld.X, pinchFingerTwoOld.Y));
            float distanceNew = Vector2.Distance(new Vector2(pinchOne.X, pinchOne.Y), new Vector2(pinchTwo.X, pinchTwo.Y));

            float scale = (distanceOld / distanceNew);

            float tempScale = this.modelScale * scale;
            
            if (tempScale <= 1.0f) this.modelScale = 1.001f;
            else if (tempScale >= 180.0f) this.modelScale = 179.89f;
            else this.modelScale = tempScale;

            pinchFingerOneOld = pinchOne;
            pinchFingerTwoOld = pinchTwo;
       }

        private void rotateZAxis(GameTime gameTime)
        {
            if (zRotationPoints.Count < 5)
            {
                foreach (TouchPoint t in touchPoints.Values)
                {
                    zRotationPoints[t.Id] = t;
                }
            }

            float sumX=0, sumY=0;
            //calculate middle point
            foreach(TouchPoint t in touchPoints.Values)
            {
                sumX += t.X;
                sumY += t.Y;
            }

            float averageX = sumX / 5;
            float averageY = sumY / 5;

            Vector2 middlePoint = new Vector2(averageX, averageY);
            Double sumAngles = 0;

            foreach (TouchPoint t in touchPoints.Values)
            {
                Vector2 oldPoint = new Vector2(zRotationPoints[t.Id].X, zRotationPoints[t.Id].Y);
                Vector2 newPoint = new Vector2(t.X, t.Y);

                float distanceOld = Vector2.Distance(middlePoint, oldPoint);
                float distanceNew = Vector2.Distance(middlePoint, newPoint);
                float distanceBetween = Vector2.Distance(oldPoint, newPoint);

                Double angle = Math.Acos((Math.Pow(distanceNew, 2) + Math.Pow(distanceOld, 2) - Math.Pow(distanceBetween, 2)) / (2* distanceNew * distanceOld));
                System.Diagnostics.Trace.WriteLine("calculated angle " + t.Id + " is " + angle);
                if(isLeft(middlePoint, oldPoint, newPoint))
                {
                    sumAngles -= angle;
                }
                else
                {
                    sumAngles += angle;
                }
            }

            float averageChangedAngle = (float)(sumAngles / 5);

            modelRotationZ += averageChangedAngle;            
            
            foreach (TouchPoint t in touchPoints.Values)
            {
                zRotationPoints[t.Id] = t;
            }
        }

        /// <summary>
        /// This is called when the app should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            if (!applicationLoadCompleteSignalled)
            {
                // Dismiss the loading screen now that we are starting to draw
                ApplicationServices.SignalApplicationLoadComplete();
                applicationLoadCompleteSignalled = true;
            }

            //TODO: Rotate the UI based on the value of screenTransform here if desired

            GraphicsDevice.Clear(backgroundColor);

            //TODO: Add your drawing code here
            //TODO: Avoid any expensive logic if application is neither active nor previewed

            //reset values that change due SpriteBatch Draw function
            GraphicsDevice.BlendState = BlendState.Opaque;
            GraphicsDevice.DepthStencilState = DepthStencilState.Default;

            Matrix[] transforms = new Matrix[myModel.Bones.Count];
            myModel.CopyAbsoluteBoneTransformsTo(transforms);

            //System.Diagnostics.Debug.WriteLine("scale will be set to : " + this.modelScale);
            foreach (ModelMesh mesh in myModel.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.EnableDefaultLighting();
                    effect.World = transforms[mesh.ParentBone.Index] *
                        Matrix.CreateRotationY(modelRotationY)
                        * Matrix.CreateRotationX(modelRotationX)
                        * Matrix.CreateRotationZ(modelRotationZ)
                        * Matrix.CreateTranslation(modelPosition);
                    effect.View = Matrix.CreateLookAt(cameraPosition, Vector3.Zero, Vector3.Up);
                    effect.Projection = Matrix.CreatePerspectiveFieldOfView(
                        MathHelper.ToRadians(modelScale), aspectRatio, 1.0f, 10000.0f);
                }

                mesh.Draw();
            }

            base.Draw(gameTime);
            drawMenu(gameTime);
        }

        private void drawMenu(GameTime gameTime)
        {
                foreach(TouchPoint menuPoint in menuPoints.Values)
                {
                    menuComponent.DrawOnLocation(gameTime, new Vector2(menuPoint.X, menuPoint.Y), menuPoint.Orientation);
                }
        }

        #endregion

        #region Application Event Handlers

        /// <summary>
        /// This is called when the user can interact with the application's window.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnWindowInteractive(object sender, EventArgs e)
        {
            //TODO: Enable audio, animations here

            //TODO: Optionally enable raw image here
        }

        /// <summary>
        /// This is called when the user can see but not interact with the application's window.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnWindowNoninteractive(object sender, EventArgs e)
        {
            //TODO: Disable audio here if it is enabled

            //TODO: Optionally enable animations here
        }

        /// <summary>
        /// This is called when the application's window is not visible or interactive.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnWindowUnavailable(object sender, EventArgs e)
        {
            //TODO: Disable audio, animations here

            //TODO: Disable raw image if it's enabled
        }

        void touchTarget_TouchDown(object sender, TouchEventArgs e)
        {
            if (!e.TouchPoint.IsTagRecognized)
            {
                touchPoints[e.TouchPoint.Id] = e.TouchPoint;
            }
            else
            {
                menuPoints[e.TouchPoint.Id] = e.TouchPoint;
            }
        }

        void touchTarget_TouchUp(object sender, TouchEventArgs e)
        {
            //if (e.TouchPoint.IsTagRecognized)
            //{
            //    showMenu = false;
            //    menuPoint = null;
            //}
            touchPoints.Remove(e.TouchPoint.Id);
            menuPoints.Remove(e.TouchPoint.Id);
            if (zRotationPoints.ContainsKey(e.TouchPoint.Id)) zRotationPoints.Remove(e.TouchPoint.Id);
        }

        #endregion

        #region Help functions

        bool isLeft(Vector2 rotationPoint, Vector2 oldPosition, Vector2 currentPosition)
        {
            return ((oldPosition.X - rotationPoint.X) * (currentPosition.Y - rotationPoint.Y) - (oldPosition.Y - rotationPoint.Y) * (currentPosition.X - rotationPoint.X)) > 0;
        }

        #endregion

        #region IDisposable

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                // Release managed resources.
                IDisposable graphicsDispose = graphics as IDisposable;
                if (graphicsDispose != null)
                {
                    graphicsDispose.Dispose();
                }
                if (touchTarget != null)
                {
                    touchTarget.Dispose();
                    touchTarget = null;
                }
            }

            // Release unmanaged Resources.

            // Set large objects to null to facilitate garbage collection.

            base.Dispose(disposing);
        }

        #endregion
    }
}
